#include "EstrategiaAlmacenamientoBloques.h"
#include "Registro.h"
#include "BloqueCompresor.h"
#include "Excepciones.h"

namespace Toolkit
{

EstrategiaAlmacenamientoBloques::EstrategiaAlmacenamientoBloques(
		const unsigned long int &tamanoBloque,
		const bool comprimir, const float &porcentajeFragmentacion) :
	EstrategiaAlmacenamiento(tamanoBloque)
{
	this-> porcentajeFragmentacion = porcentajeFragmentacion;
	this-> comprimir = comprimir;
}

EstrategiaAlmacenamientoBloques::~EstrategiaAlmacenamientoBloques()
{
}

unsigned long int EstrategiaAlmacenamientoBloques::guardarDato(
		Almacenamiento &almacenamiento, const Dato &dato, const Clave &clave,
		WrapperComponente &wrapper)
{
	if (datoAuxiliar == NULL)
	{
		datoAuxiliar = dato.clonar();
	}

	//guardo una copia auxiliar del wrapper;
	WrapperComponente *wrapperAuxiliar = wrapper.clonarSinComponente();
	Registro *registro = new Registro(BINARIO);
	registro->setDato(dato);

	Bloque *bloqueNuevo = nuevoBloque();
	Bloque *bloqueAnterior = nuevoBloque();

	if (!bloqueNuevo->puedeAgregarse(registro))
	{ //el dato no entra en un bloque vacío
		delete registro;
		delete bloqueNuevo;
		delete bloqueAnterior;
		delete wrapperAuxiliar;
		throw DesbordeDeDatoException();
	}

	bool crearNuevo = true;

	IndiceEspacio back;
	back.posicion = 0;
	back.bytesDisponibles = 0;
	if (almacenamiento.getFragmentacion().size() > 0)
	{
		back = almacenamiento.getFragmentacion().back();
		wrapperAuxiliar->setComponente(bloqueAnterior);

		stringstream serialAnterior;
		almacenamiento.recuperarDato(back.posicion, serialAnterior);
		wrapperAuxiliar->hidratar(serialAnterior, dato, BINARIO);
		crearNuevo = (!bloqueAnterior->puedeAgregarse(registro));
	}

	unsigned long int laPosicion;
	if (crearNuevo)
	{
		//Lo ubico en un nuevo bloque
		bloqueNuevo->agregar(registro, clave);
		wrapper.setComponente(bloqueNuevo);

		stringstream serialWrapper;
		wrapper.serializar(serialWrapper, BINARIO);

		try
		{
			laPosicion = almacenamiento.guardarDato(serialWrapper);
		} catch (BufferOverflowException &e)
		{
			delete bloqueAnterior;
			delete wrapperAuxiliar;
			delete bloqueNuevo;
			throw e;
		}

		controlarFragmentacion(*bloqueNuevo, laPosicion, almacenamiento);
	}
	else
	{
		//Intento ubicarlo en uno de los bloques fragmentados, worst fit
		laPosicion = back.posicion;
		removerIndice(laPosicion, almacenamiento);

		almacenar(*bloqueAnterior, registro, clave, *wrapperAuxiliar,
				almacenamiento, laPosicion);
	}

	delete bloqueNuevo;
	delete bloqueAnterior;
	delete wrapperAuxiliar;
	return laPosicion;
}

unsigned long int EstrategiaAlmacenamientoBloques::modificarDato(
		Almacenamiento &almacenamiento, const Dato &dato, const Clave &clave,
		const unsigned long int posicion, WrapperComponente &wrapper)
{
	if (datoAuxiliar == NULL)
	{
		datoAuxiliar = dato.clonar();
	}

	Bloque *bloque = nuevoBloque();
	Registro *registro = new Registro(BINARIO);
	registro->setDato(dato);

	if (!bloque->puedeAgregarse(registro))
	{
		delete registro;
		delete bloque;
		throw DesbordeDeDatoException();
	}

	WrapperComponente *wrapperAuxiliar = wrapper.clonarSinComponente();

	stringstream serialWrapper;
	wrapper.setComponente(bloque);
	almacenamiento.recuperarDato(posicion, serialWrapper);
	wrapper.hidratar(serialWrapper, dato, BINARIO);
	delete bloque->remover(clave);

	unsigned long int laPosicion;
	//si esta en la lista de espacios, lo saco
	removerIndice(posicion, almacenamiento);

	if (bloque->puedeAgregarse(registro))
	{
		almacenar(*bloque, registro, clave, wrapper, almacenamiento, posicion);
		laPosicion = posicion;

	}
	else
	{
		actualizarEliminacionRegistro(*bloque, wrapper, almacenamiento,
				posicion);

		laPosicion = guardarDato(almacenamiento, dato, clave, *wrapperAuxiliar);
		delete registro;
	}

	delete wrapperAuxiliar;
	delete bloque;
	return laPosicion;
}

bool EstrategiaAlmacenamientoBloques::recuperarDato(
		Almacenamiento &almacenamiento, const unsigned long int posicion,
		Dato &dato, const Clave &clave, WrapperComponente &wrapper)
{
	if (datoAuxiliar == NULL)
	{
		datoAuxiliar = dato.clonar();
	}

	Bloque *bloque = nuevoBloque();
	wrapper.setComponente(bloque);

	stringstream serializacion;
	try{
	    almacenamiento.recuperarDato(posicion, serializacion);
	}
	catch(PosicionInvalidaException &e){
	    delete bloque;
	    throw e;
	}
	catch(UnidadEliminadaException &e){
		delete bloque;
		throw e;
	}

	wrapper.hidratar(serializacion, dato, BINARIO);

	Registro *registro = dynamic_cast<Registro*> (bloque->buscar(clave));

	bool resultado = false;
	if (registro != NULL)
	{
		registro->hidratarDato(dato);
		resultado = true;
	}

	delete bloque;
	return resultado;

}

void EstrategiaAlmacenamientoBloques::eliminarDato(
		Almacenamiento &almacenamiento, const long unsigned int posicion,
		Dato &dato, const Clave &clave, WrapperComponente &wrapper)
{
	if (datoAuxiliar == NULL)
	{
		datoAuxiliar = dato.clonar();
	}

	stringstream serialWrapper;
	Bloque *bloque = nuevoBloque();
	wrapper.setComponente(bloque);
	almacenamiento.recuperarDato(posicion, serialWrapper);

	wrapper.hidratar(serialWrapper, dato, BINARIO);
	Registro *registro = dynamic_cast<Registro*> (bloque->remover(clave));
	removerIndice(posicion, almacenamiento);

	actualizarEliminacionRegistro(*bloque, wrapper, almacenamiento, posicion);

	if (registro != NULL)
	{
		registro->hidratarDato(dato);
		delete registro;
	}

	delete bloque;

}

/*** metodos privados ***/
void EstrategiaAlmacenamientoBloques::controlarFragmentacion(
		const Bloque &bloque, const unsigned long int &posicion,
		Almacenamiento &almacenamiento)
{
	IndiceEspacio indice;
	indice.posicion = posicion;
	indice.bytesDisponibles = bloque.espacioLibre();

	//si el bloque esta vacío se supone que se marcara como eliminado en el
	//almacenamiento, entonces no estara en la lista.
	if ((bloque.cantElementos() > 0) && (indice.bytesDisponibles
			>= (porcentajeFragmentacion * bloque.getTamanoBloque())))
	{
		list<IndiceEspacio> aux;
		list<IndiceEspacio> espacios = almacenamiento.getFragmentacion();

		aux.push_front(indice);
		espacios.merge(aux);
		almacenamiento.setFragmentacion(espacios);

	}
}

void EstrategiaAlmacenamientoBloques::almacenar(Bloque &bloque,
		Registro *registro, const Clave &clave, WrapperComponente &wrapper,
		Almacenamiento &almacenamiento, const long unsigned int &posicion)
{

	bloque.agregar(registro, clave);

	wrapper.setComponente(&bloque);

	stringstream serialBloque;
	wrapper.serializar(serialBloque, BINARIO);

	almacenamiento.guardarDato(serialBloque, posicion);

	controlarFragmentacion(bloque, posicion, almacenamiento);

}

void EstrategiaAlmacenamientoBloques::removerIndice(
		const long unsigned int &posicion, Almacenamiento &almacenamiento)
{
	list<IndiceEspacio> espacios = almacenamiento.getFragmentacion();

	list<IndiceEspacio>::iterator it = espacios.begin();
	while (it != espacios.end())
	{
		if (it-> posicion == posicion)
		{
			espacios.erase(it);
			break;
		}

		it++;
	}

	almacenamiento.setFragmentacion(espacios);
}

void EstrategiaAlmacenamientoBloques::actualizarEliminacionRegistro(
		Bloque &bloque, WrapperComponente &wrapper,
		Almacenamiento &almacenamiento, const long unsigned int &posicion)
{
	stringstream serialWrapper;
	if (bloque.cantElementos() > 0)
	{
		//se vuelve a guardar el bloque y se controla fragmentacion
		wrapper.setComponente(&bloque);
		wrapper.serializar(serialWrapper, BINARIO);
		almacenamiento.guardarDato(serialWrapper, posicion);

		controlarFragmentacion(bloque, posicion, almacenamiento);
	}
	else
	{
		//El bloque quedo vacío, se lo quita del almacenamiento
		almacenamiento.eliminarDato(posicion, serialWrapper);
	}
}

void EstrategiaAlmacenamientoBloques::recuperarComponente(
		Almacenamiento &almacenamiento, const unsigned long int &posicion,
		WrapperComponente &wrapper)
{
	stringstream stream;
	almacenamiento.recuperarDato(posicion, stream);

	Bloque *bloque = new Bloque(tamanoBloque, BINARIO);

	wrapper.setComponente(bloque);
	wrapper.hidratar(stream, *datoAuxiliar, BINARIO);

}

void EstrategiaAlmacenamientoBloques::guardarComponente(
		Almacenamiento &almacenamiento, const unsigned long int &posicion,
		WrapperComponente &wrapper)
{
	stringstream stream;
	wrapper.serializar(stream, BINARIO);

	almacenamiento.guardarDato(stream, posicion);

}

unsigned long int EstrategiaAlmacenamientoBloques::guardarComponente(
		Almacenamiento &almacenamiento, WrapperComponente &wrapper)
{
	stringstream stream;
	wrapper.serializar(stream, BINARIO);

	return almacenamiento.guardarDato(stream);
}

Bloque *EstrategiaAlmacenamientoBloques::nuevoBloque()
{
	if (comprimir)
	{
		return new BloqueCompresor(tamanoBloque, BINARIO);
	}
	else
	{
		return new Bloque(tamanoBloque, BINARIO);
	}
}

void EstrategiaAlmacenamientoBloques::eliminarComponente(
		Almacenamiento &almacenamiento, const unsigned long int &posicion,
		WrapperComponente &wrapper)
{
	stringstream stream;
	almacenamiento.eliminarDato(posicion, stream);

	wrapper.hidratar(stream, *datoAuxiliar, BINARIO);
	removerIndice(posicion, almacenamiento);
}

}
